%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Industrial Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% Any file used by the particular function whose handle is passed in the
% input argument "readFramesFunctHandle". Those may include:
% patientlyKeepTryingToReadUntilErrorFree.m
% readAndCheckVideoFrames.m
% readImageFile.m
% readImagesByIndexRange.m
% readVideoFrames.m 
% readdpx.m
% Image processing toolbox
%
%==========================================================================

%==========================================================================
% The function "getSoundTrackBoundsManually" is an interactive tool for
% manually getting the left and right bounds of the sound track region in 
% the given frames.
% INPUT: "readFramesFunctHandle" is a handle to a function capable of
% reading any range of frames from a preset source.
% "frameIndArr" is a vector of integers integers specifying the indices of
% the frames to be read from a preset source.
% "leftBound" and "rightBound" specify the left and right vertical bounds
% of an initial draggable rectangle which is to be adjust by the user to
% match the sound track area. Selecting a rectangle that is smaller than
% the sound track area is usually acceptable but selecting a rectangle that
% is smaller than may have a detrimental effect on sound reconstruction.
% OUTPUT: "leftBound" and "rightBound" return the left and right vertical
% bounds of the common sound track region in the given frames.
%==========================================================================
function [leftBound, rightBound] = getSoundTrackBoundsManually(readFramesFunctHandle, ...
    frameIndArr, leftBound, rightBound)

% Specify that read frames are not to be converted to grayscale.
doConvertToGray = false;

% Read in the first frame.
[frameArr, bitDepth] = readFramesFunctHandle(frameIndArr(1), doConvertToGray);

% Scale pixel values in case they are integers.
if (isinteger(frameArr) == true)
    % Compute the number of bits used to store each pixel value.
    storageBitDepth = round(log2(double(intmax(class(frameArr)))));
    
    % Check if the type used to store the pixel values can accomodate them.
    assert((storageBitDepth >= bitDepth), ...
        'SoundRestoration:getSoundTrackBoundsManually:TooSmallStorageType', ...
        'Type used to store the pixel values is too small to accomodate them');
    
    % Scale pixel values to the full range offered by the type used to 
    % store them.
    scale = 2^(storageBitDepth - bitDepth);
    frameArr = frameArr*scale;
end

% Create a new figure and keep a handle to it.
figHandle = figure('Toolbar', 'none', 'Menubar', 'none');
% Turn off any warning that may result from the fact that the display size
% is smaller than the image size, and the image can not be embedded in the
% screen at its full resolution.
warning('off', 'Images:initSize:adjustingMag');
% Create an image object out of the given frame array.
imageHandle = imshow(frameArr);
% Maximize the figure to cover the whole screen.
set(figHandle, 'Position', get(0, 'ScreenSize'));

% Create a scroll panel showing the image in the set figure.
scrPanHandle = imscrollpanel(figHandle, imageHandle);
%set(scrPanHandle, 'Units', 'normalized', 'Position', [0 .1 1 .9])
% Arrange for positions to be specified in pixels. 
set(scrPanHandle, 'Units', 'pixel');

% Get the structure of function handles capable of manipulating the scroll
% panel.
api = iptgetapi(scrPanHandle);
% Get the lower left corner, width and height of the visible portion of the
% image.
visiblePositionArr = api.getVisibleImageRect();
% Set the default visible location so that the upper end of the sound track
% area is placed as much as possible in the middle of the visible portion.
api.setVisibleLocation(max(0, round((leftBound + rightBound - visiblePositionArr(3))/2)), 0);
% Squeeze the scroll panel a little bit to make room for the title and
% instructions texts.
set(scrPanHandle, 'Units', 'normalized', 'Position', [0, 0.1, 1, 0.85]);


% Create a draggable rectangle with sides parallel to the coordinates axes
% around what is thought to be the sound track region of the current scan.
rectHandle = imrect(gca, [leftBound 0 (rightBound - leftBound) size(frameArr, 1)]);
% Set the color in which the rectangle is drawn to red.
setColor(rectHandle, 'r');
% Restrict the movement of the draggable rectangle to within the current
% image.
functHandle = makeConstrainToRectFcn('imrect', get(gca, 'XLim'), get(gca, 'YLim'));
setPositionConstraintFcn(rectHandle, functHandle);


% Create invisible axes and place them as desired. They will be referred to
% when the title and instructions block are placed on the figure. The axes
% of the scroll panel will not do because they are completely covered by
% the scroll panel which leaves any text hidden under it.
axesHandle = axes('Units', 'normalized', 'Position', [0 0 1 1], 'Visible', 'off');
% Get a handle to the title of the invisible axes and make it visible.
set(get(axesHandle, 'Title'), 'Visible', 'on');
% Use a title placed above the image to show the initial horizontal range
% of the draggable rectangle.
title(['Selected horizontal range (in pixels): ' num2str(leftBound) ':' num2str(rightBound)], 'Position', [0.5, 0.96]);
% Provide instructions under the image.
text(0.1, 0.07, char('{\it \color{blue}Instructions}: (1) If necessary, move the vertical red lines to match the sound track area.', ...
    '                    (2) To confirm, double-click between them.'), 'Units', 'normalized');
% Provide hints under the image.
text(0.5, 0.07, char('{\it \color{blue}Hints}: (1) Start wide.', ...
    '           (2) To start over just close this window.'), 'Units', 'normalized');

% Have a title showing the current horizontal range of the draggable
% rectangle that updates any time the rectangle's position is changed.
addNewPositionCallback(rectHandle, @(p) ...
    title(axesHandle, ['Selected horizontal range (in pixels): ' ...
    num2str(round(p(1))) ':' num2str(round(p(1) + p(3)))], 'Position', [0.5, 0.96]));

% Have the image automatically shift when the draggable rectangle hits the
% left or right boundary of the screen.
addNewPositionCallback(rectHandle, @(p) ...
    scrollAtEdge(p, iptgetapi(scrPanHandle), size(frameArr, 2)));
    
% Get the last position of the draggable rectangle when the user
% double-clicked on it.
positionArr = wait(rectHandle);
% Reset the left and right bounds of the sound track region. The position
% array "positionArr" is of the form [xmin ymin width height].
leftBound = round(positionArr(1));
rightBound = round(leftBound + positionArr(3));


% Process the rest of the frames.
for ind = 2:numel(frameIndArr)
    
    % Read in the current frame.
    frameArr = readFramesFunctHandle(frameIndArr(ind), doConvertToGray);
    
    if (isinteger(frameArr) == true)
        % Scale pixel values to the full range offered by the type used to
        % store them.
        frameArr = frameArr*scale;
    end

    % Replaces the image displayed in the scroll panel while preserving the
    % current magnification and centering of the image.
    api.replaceImage(frameArr, [0, 2^storageBitDepth - 1], 'PreserveView', true);
  
    % Adjust the draggable rectangle to a reflect the new left and right
    % bounds.
    setPosition(rectHandle, [leftBound 0 (rightBound - leftBound) size(frameArr, 1)]);

    % Have a title showing the current horizontal range of the draggable
    % rectangle that updates any time the rectangle's position is changed.
    addNewPositionCallback(rectHandle, @(p) ...
        title(axesHandle, ['Selected horizontal range (in pixels): ' ...
        num2str(round(p(1))) ':' num2str(round(p(1) + p(3)))], 'Position', [0.5, 0.96]));
    
    % Get the last position of the draggable rectangle when the user
    % double-clicked on it. The position array "positionArr" is of the form
    % [xmin ymin width height].
    positionArr = wait(rectHandle);
    % Update the left and right vertical bounds of the sound track region.
    leftBound = max(leftBound, round(positionArr(1)));
    rightBound = min(rightBound, round(positionArr(1) + positionArr(3)));
end

% Close the figure.
close(figHandle);
pause on
pause(0);

return;
% end of the function "getSoundTrackBoundsManually"



%==========================================================================
% The function "scrollAtEdge" scrolls the window if the selection bumps
% the edge of the window.
% INPUT: "pos" is a position of the selection.
% "api" is the interface to the scroll window.
% "imgWidth" is the width of the image.
% OUTPUT: none.
%==========================================================================
function scrollAtEdge(pos, api, imgWidth)
vis = api.getVisibleImageRect();
xmin = vis(1);
ymin = vis(2);
width = vis(3);

left = xmin;
right = xmin + width;
l = pos(1);
w = pos(3);
r = l + w;

if(left > 0 && (l - left) < 10)
    jump = (width / 2);
    left = left - jump;
    if(left < 1)
        left = 1;
    end
    api.setVisibleLocation(left, ymin);
elseif(right < imgWidth && (right - r) < 10)
    jump = (width / 2);
    right = right + jump;
    if(right > imgWidth)
        right = imgWidth;
    end
    api.setVisibleLocation(right, ymin);
end

return;
% end of the function "scrollAtEdge"
